package com.example.test;

import static org.lwjgl.opengl.GL11.*;

import java.util.HashSet;
import java.util.Set;

import javax.vecmath.Matrix4f;
import javax.vecmath.Quat4f;
import javax.vecmath.Vector3f;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.glu.Sphere;

import com.bulletphysics.collision.broadphase.BroadphaseInterface;
import com.bulletphysics.collision.broadphase.DbvtBroadphase;
import com.bulletphysics.collision.dispatch.CollisionConfiguration;
import com.bulletphysics.collision.dispatch.CollisionDispatcher;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.dispatch.DefaultCollisionConfiguration;
import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.collision.shapes.SphereShape;
import com.bulletphysics.collision.shapes.StaticPlaneShape;
import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.dynamics.RigidBodyConstructionInfo;
import com.bulletphysics.dynamics.constraintsolver.ConstraintSolver;
import com.bulletphysics.dynamics.constraintsolver.SequentialImpulseConstraintSolver;
import com.bulletphysics.linearmath.DefaultMotionState;
import com.bulletphysics.linearmath.MotionState;
import com.bulletphysics.linearmath.Transform;

public class Launcher {
	private static final String WINDOW_TITLE = "";
    private static final int[] WINDOW_DIMENSIONS = {640, 480};
    private static final Transform DEFAULT_BALL_TRANSFORM = new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(0, 35, 0), 1.0f));

    private static DiscreteDynamicsWorld dynamicsWorld;

    private static Set<RigidBody> balls = new HashSet<RigidBody>();
    private static Set<RigidBody> blocks = new HashSet<RigidBody>();

    private static RigidBody controlBall;
    private static EulerCamera camera = new EulerCamera.Builder()
            .setFieldOfView(300)
            .setNearClippingPane(0.3f)
            .setFarClippingPane(500)
            .setPosition(0, 25, 15)
            .build();
    
    private static boolean applyForce = false;
    private static boolean createNewShape = false;
    private static boolean resetControlBall = false;
    
    private static Sphere sphere = new Sphere();

    
    public static void main(String[] args) {
        setUpDisplay();
        setUpStates();
        setUpMatrices();
        setUpPhysics();
        enterGameLoop();
        cleanUp(false);
    }

    
    private static void setUpDisplay() {
        try {
            Display.setVSyncEnabled(true);
            Display.setDisplayMode(new DisplayMode(WINDOW_DIMENSIONS[0], WINDOW_DIMENSIONS[1]));
            Display.setTitle(WINDOW_TITLE);
            Display.create();
        } catch (LWJGLException e) {
            e.printStackTrace();
            cleanUp(true);
        }
    }
    

    private static void setUpStates() {
        camera.applyOptimalStates();
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
        glLineWidth(2);
    }
    

    private static void setUpMatrices() {
        camera.setAspectRatio((float) Display.getWidth() / (float) Display.getHeight());
        camera.setFieldOfView(65);
        camera.applyPerspectiveMatrix();
    }
    
    
    private static void setUpPhysics() {
        BroadphaseInterface broadphase = new DbvtBroadphase();
        CollisionConfiguration collisionConfiguration = new DefaultCollisionConfiguration();
        CollisionDispatcher dispatcher = new CollisionDispatcher(collisionConfiguration);
        ConstraintSolver solver = new SequentialImpulseConstraintSolver();
        
        dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
        dynamicsWorld.setGravity(new Vector3f(0, -10, 0));
        
        CollisionShape groundShape = new StaticPlaneShape(new Vector3f(0, 1, 0), 0.25f);
        CollisionShape ballShape = new SphereShape(3);
        MotionState groundMotionState = new DefaultMotionState(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(0, 0, 0), 1.0f)));
        RigidBodyConstructionInfo groundBodyConstructionInfo = new RigidBodyConstructionInfo(0, groundMotionState, groundShape, new Vector3f(0, 0, 0));
        
        groundBodyConstructionInfo.restitution = 0.25f;
        
        RigidBody groundRigidBody = new RigidBody(groundBodyConstructionInfo);
       
        dynamicsWorld.addRigidBody(groundRigidBody);
        
        MotionState ballMotion = new DefaultMotionState(new Transform(DEFAULT_BALL_TRANSFORM));
        
        Vector3f ballInertia = new Vector3f(0, 0, 0);
        ballShape.calculateLocalInertia(2.5f, ballInertia);
        
        RigidBodyConstructionInfo ballConstructionInfo = new RigidBodyConstructionInfo(2.5f, ballMotion, ballShape, ballInertia);
        
        ballConstructionInfo.restitution = 0.5f;
        ballConstructionInfo.angularDamping = 0.95f;
        
        controlBall = new RigidBody(ballConstructionInfo);
        controlBall.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
        balls.add(controlBall);
        dynamicsWorld.addRigidBody(controlBall);
    }
    

    private static void cleanUp(boolean asCrash) {
        Display.destroy();
        System.exit(asCrash ? 1 : 0);
    }

    

    private static void enterGameLoop() {
        while (!Display.isCloseRequested()) {
            render();
            logic();
            input();
            update();
        }
    }
    

    private static void render() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        for (RigidBody body : balls) {
            glPushMatrix();
	            Vector3f ballPosition = body.getWorldTransform(new Transform()).origin;
	            glTranslatef(ballPosition.x, ballPosition.y, ballPosition.z);
	            
	            sphere.setDrawStyle(GLU.GLU_SILHOUETTE);
	            
	            if (body.equals(controlBall)) {
	                glColor4f(0, 1, 0, 1);
	            } else {
	                glColor4f(1, 0, 0, 1);
	            }
	            
	            sphere.draw(3, 30, 30);
            glPopMatrix();
        }
        
        for(RigidBody block : blocks) {
        	glPushMatrix();
        	
        	glPopMatrix();
        }
        
        
        glBegin(GL_QUADS);
	        glColor4f(0.6f, 0.6f, 0.6f, 1);
	        glVertex4f(-50, 0, -50, 1);
	        
	        glColor4f(0.85f, 0.85f, 0.85f, 1);
	        glVertex4f(-50, 0, +50, 1);
	        
	        glColor4f(0.75f, 0.75f, 0.75f, 1);
	        glVertex4f(+50, 0, +50, 1);
	        
	        glColor4f(0.5f, 0.5f, 0.5f, 1);
	        glVertex4f(+50, 0, -50, 1);
        glEnd();
    }
    
    
    private static void logic() {
        glLoadIdentity();
        
        camera.applyTranslations();
        dynamicsWorld.stepSimulation(1 / 60.0f);
        
        Set<RigidBody> bodiesToBeRemoved = new HashSet<RigidBody>();
        
        for (RigidBody body : balls) {
            Vector3f position = body.getMotionState().getWorldTransform(new Transform()).origin;
            
            if (!body.equals(controlBall) && (position.x < -50 || position.x > 50 || position.z < -50 || position.z > 50)) {
                bodiesToBeRemoved.add(body);
            }
        }
        
        for (RigidBody body : bodiesToBeRemoved) {
            dynamicsWorld.removeRigidBody(body);
            balls.remove(body);
        }
        
        if (applyForce) {
            Transform controlBallTransform = new Transform();
            controlBall.getMotionState().getWorldTransform(controlBallTransform);
            
            Vector3f controlBallLocation = controlBallTransform.origin;
            Vector3f cameraPosition = new Vector3f(camera.x(), camera.y(), camera.z());
            Vector3f force = new Vector3f();
            
            force.sub(cameraPosition, controlBallLocation);
            
            controlBall.activate(true);
            controlBall.applyCentralForce(force);
        }
        
        if (createNewShape) {
            CollisionShape shape = new SphereShape(3);
            
            DefaultMotionState motionState = new DefaultMotionState(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(camera.x(), 35, camera.z()), 1.0f)));
            
            Vector3f inertia = new Vector3f(0, 0, 0);
            shape.calculateLocalInertia(1.0f, inertia);
            
            RigidBodyConstructionInfo constructionInfo = new RigidBodyConstructionInfo(1, motionState, shape, inertia);
            constructionInfo.restitution = 0.75f;
            
            RigidBody rigidBody = new RigidBody(constructionInfo);
            
            balls.add(rigidBody);
            dynamicsWorld.addRigidBody(rigidBody);
            createNewShape = false;
        }

        if (resetControlBall) {
            controlBall.setCenterOfMassTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(0, 50, 0), 1.0f)));
            controlBall.setAngularVelocity(new Vector3f(0, 0, 0));
            controlBall.setLinearVelocity(new Vector3f(0, 0, 0));
            resetControlBall = false;
        }
    }
    
    
    private static void input() {
        if (Mouse.isGrabbed()) {
            camera.processMouse(1.0f, 80, -80);
        }
        
        camera.processKeyboard(1000.0f / 60.0f, 4);
        
        if (Mouse.isButtonDown(0) && !Mouse.isGrabbed()) {
            Mouse.setGrabbed(true);
        } else if (Mouse.isButtonDown(1) && Mouse.isGrabbed()) {
            Mouse.setGrabbed(false);
        } else if (Mouse.isButtonDown(0) && Mouse.isGrabbed()) {
            applyForce = true;
        } else {
            applyForce = false;
        }
        
        while (Keyboard.next()) {
            if (Keyboard.getEventKeyState()) {
                if (Keyboard.getEventKey() == Keyboard.KEY_G) {
                    createNewShape = true;
                }
                
                if (Keyboard.getEventKey() == Keyboard.KEY_F) {
                    resetControlBall = true;
                }
            }
        }
    }
    
    
    private static void update() {
        Display.update();
        Display.sync(60);
    }
}
